﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using FlickrNet;
using Sample.PhotoBrowser.Components;
using Sample.PhotoBrowser.Properties;
using ZOIL.DataBackend;
using ZOIL.DataModel;
using ZOIL.Framework.Core;
using ZOIL.Library.Behaviors;
using ZOIL.Library.Components;

namespace Sample.PhotoBrowser
{
    public partial class MainWindow : INotifyPropertyChanged
    {
        #region private fields

        private BackgroundWorker _searchByWordWorker;

        private const string FlickrApiKey = "c705bfbf75e8d40f584c8a946cf0834c";

        private Flickr _flickr;

        #endregion

        #region public properties

        #region ElementCount

        private int _elementCount;

        /// <summary>
        /// Gets or sets the ElementCount property. This observable property 
        /// indicates ....
        /// </summary>
        public int ElementCount
        {
            get { return _elementCount; }
            set
            {
                if (_elementCount != value)
                {
                    int old = _elementCount;
                    _elementCount = value;
                    OnElementCountChanged(old, value);
                    RaisePropertyChanged("ElementCount");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ElementCount property.
        /// </summary>
        protected virtual void OnElementCountChanged(int oldElementCount, int newElementCount)
        {
        }

        #endregion

        #region IsSearchActive

        private bool _isSearchActive;

        /// <summary>
        /// Gets or sets the IsSearchActive property. This observable property 
        /// indicates ....
        /// </summary>
        public bool IsSearchActive
        {
            get { return _isSearchActive; }
            set
            {
                if (_isSearchActive != value)
                {
                    bool old = _isSearchActive;
                    _isSearchActive = value;
                    OnIsSearchActiveChanged(old, value);
                    RaisePropertyChanged("IsSearchActive");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsSearchActive property.
        /// </summary>
        protected virtual void OnIsSearchActiveChanged(bool oldIsSearchActive, bool newIsSearchActive)
        {
        }

        #endregion

        #region SearchProgress

        private int _searchProgress;

        /// <summary>
        /// Gets or sets the SearchProgress property. This observable property 
        /// indicates
        /// </summary>
        public int SearchProgress
        {
            get { return _searchProgress; }
            set
            {
                if (_searchProgress != value)
                {
                    int old = _searchProgress;
                    _searchProgress = value;
                    OnSearchProgressChanged(old, value);
                    RaisePropertyChanged("SearchProgress");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the SearchProgress property.
        /// </summary>
        protected virtual void OnSearchProgressChanged(int oldSearchProgress, int newSearchProgress)
        {
        }

        #endregion

        #region SearchText

        private String _searchText;

        /// <summary>
        /// Gets or sets the SearchText property. This observable property 
        /// indicates
        /// </summary>
        public String SearchText
        {
            get { return _searchText; }
            set
            {
                if (_searchText != value)
                {
                    String old = _searchText;
                    _searchText = value;
                    OnSearchTextChanged(old, value);
                    RaisePropertyChanged("SearchText");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the SearchText property.
        /// </summary>
        protected virtual void OnSearchTextChanged(String oldSearchText, String newSearchText)
        {
        }

        #endregion

        #region DynamicQueryText

        private String _dynamicQueryText;

        /// <summary>
        /// Gets or sets the DynamicQueryText property. This observable property 
        /// indicates
        /// </summary>
        public String DynamicQueryText
        {
            get { return _dynamicQueryText; }
            set
            {
                if (_dynamicQueryText != value)
                {
                    String old = _dynamicQueryText;
                    _dynamicQueryText = value;
                    OnDynamicQueryTextChanged(old, value);
                    RaisePropertyChanged("DynamicQueryText");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the DynamicQueryText property.
        /// </summary>
        protected virtual void OnDynamicQueryTextChanged(String oldDynamicQueryText, String newDynamicQueryText)
        {
            DynamicQueryOnRootCollection(newDynamicQueryText);
        }

        #endregion

        #region IsLandscapeInputEnabled

        private bool _isLandscapeInputEnabled = true;

        /// <summary>
        /// Gets or sets the IsLandscapeInputEnabled property. This observable property 
        /// indicates
        /// </summary>
        public bool IsLandscapeInputEnabled
        {
            get { return _isLandscapeInputEnabled; }
            set
            {
                if (_isLandscapeInputEnabled != value)
                {
                    var old = _isLandscapeInputEnabled;
                    _isLandscapeInputEnabled = value;
                    OnIsLandscapeInputEnabledChanged(old, _isLandscapeInputEnabled);
                    RaisePropertyChanged("IsLandscapeInputEnabled");
                }

            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the IsLandscapeInputEnabled property.
        /// </summary>
        protected virtual void OnIsLandscapeInputEnabledChanged(bool oldIsLandscapeInputEnabled, bool newIsLandscapeInputEnabled)
        {
        }

        #endregion

        #region IsSelectionEnabled

        /// <summary>
        /// Gets or sets the IsSelectionEnabled property. This observable property 
        /// indicates
        /// </summary>
        public bool IsSelectionEnabled
        {
            get { return MySelectionBehavior.IsEnabled; }
            set
            {
                if (MySelectionBehavior.IsEnabled == value) return;

                var old = MySelectionBehavior.IsEnabled;
                MySelectionBehavior.IsEnabled = value;
                OnIsSelectionEnabledChanged(old, MySelectionBehavior.IsEnabled);
                RaisePropertyChanged("IsSelectionEnabled");
            }
        }

        protected virtual void OnIsSelectionEnabledChanged(bool oldIsSelectionEnabled, bool newIsSelectionEnabled)
        {
            IsLandscapeInputEnabled = !newIsSelectionEnabled;
        }

        #endregion

        #region IsConnectionEnabled

        /// <summary>
        /// Gets or sets the IsConnectionEnabled property. This observable property 
        /// indicates
        /// </summary>
        public bool IsConnectionEnabled
        {
            get { return MyConnectionBehavior.IsEnabled; }
            set
            {
                if (MyConnectionBehavior.IsEnabled == value) return;
                MyConnectionBehavior.IsEnabled = value;
                RaisePropertyChanged("IsConnectionEnabled");
            }
        }

        #endregion

        #region MyRootCollectionViewModel

        private PersistableCollectionViewModel _rootCollectionViewModel;

        /// <summary>
        /// Gets or sets the MyRootCollectionViewModel property. This observable property 
        /// indicates
        /// </summary>
        public PersistableCollectionViewModel MyRootCollectionViewModel
        {
            get { return _rootCollectionViewModel; }
            set
            {
                if (_rootCollectionViewModel != value)
                {
                    var old = _rootCollectionViewModel;
                    _rootCollectionViewModel = value;
                    OnMyRootCollectionViewModelChanged(old, value);
                    RaisePropertyChanged("MyRootCollectionViewModel");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the MyRootCollectionViewModel property.
        /// </summary>
        protected virtual void OnMyRootCollectionViewModelChanged(PersistableCollectionViewModel oldMyRootCollection, PersistableCollectionViewModel newMyRootCollection)
        {

        }

        #endregion

        #region MyRootCollection

        private IPersistableCollection _myRootCollection;

        /// <summary>
        /// Gets or sets the MyRootCollection property. This observable property 
        /// indicates
        /// </summary>
        public IPersistableCollection MyRootCollection
        {
            get { return _myRootCollection; }
            set
            {
                if (_myRootCollection != value)
                {
                    var old = _myRootCollection;
                    _myRootCollection = value;
                    OnMyRootCollectionChanged(old, value);
                    RaisePropertyChanged("MyRootCollection");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the MyRootCollection property.
        /// </summary>
        protected virtual void OnMyRootCollectionChanged(IPersistableCollection oldMyRootCollection, IPersistableCollection newMyRootCollection)
        {

        }

        #endregion

        #endregion

        #region ctor

        public MainWindow()
        {
            InitializeComponent();

#if DEBUG
            WindowState = WindowState.Normal;
            WindowStyle = WindowStyle.ThreeDBorderWindow;
#else
            WindowState = WindowState.Maximized;
            WindowStyle = WindowStyle.None;
#endif

            Loaded += OnLoaded;
            Closed += MainWindow_Closing;

            KeyUp += OnFunctionKeys;
            KeyDown += OnFunctionKeys;
        }

        void MainWindow_Closing(object sender, EventArgs e)
        {
            var dc = DataBackendSelection.Instance;

            if (dc.ActiveConnection != null)
                dc.ActiveConnection.Dispose();
        }

        void OnFunctionKeys(object sender, KeyEventArgs e)
        {
            if (e.IsRepeat) return;

            switch (e.Key)
            {
                case Key.LeftCtrl:
                case Key.RightCtrl:
                    IsLandscapeInputEnabled = e.IsUp;
                    IsSelectionEnabled = e.IsDown;
                    IsConnectionEnabled = false;

                    MySelectionBehavior.SelectionMode = e.IsDown ? SelectionModes.FreeShape : SelectionModes.BoundingBox;
                    break;
                case Key.LeftShift:
                case Key.RightShift:
                    IsLandscapeInputEnabled = e.IsUp;
                    IsSelectionEnabled = false;
                    IsConnectionEnabled = e.IsDown;
                    break;
            }
        }

        #endregion

        #region private methods

        private static IPersistableCollection LoadCollection()
        {
            return DatabaseCollection.Collection;
        }

        private void LoadPictures(object sender, DoWorkEventArgs e)
        {
            var backgroundWorker = sender as BackgroundWorker;
            if (backgroundWorker != null)
                backgroundWorker.ReportProgress(0);

            var s = Settings.Default;

            var searchOptions = new PhotoSearchOptions
                                    {
                                        Tags = e.Argument as string,
                                    };

#if DEBUG
            //künstlich limtieren damit schneller testen
            var allPhotos = _flickr.PhotosSearch(searchOptions).Take(5).ToList();
#else
            var allPhotos = _flickr.PhotosSearch(searchOptions);
#endif

            var currentPhoto = 1;

            const string randomDescription = "No description found.";

            var random = new Random();


            foreach (var flickrPhoto in allPhotos)
            {

                if (backgroundWorker != null && backgroundWorker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }

                {
                    var perc = (int)(((currentPhoto - 0.8) / allPhotos.Count) * 100);
                    if (backgroundWorker != null)
                        backgroundWorker.ReportProgress(perc);
                }

                var person = _flickr.PeopleGetInfo(flickrPhoto.UserId);

                {
                    var perc = (int)(((currentPhoto - 0.5) / allPhotos.Count) * 100);
                    if (backgroundWorker != null)
                        backgroundWorker.ReportProgress(perc);
                }

                var photo = new FlickrPhoto
                             {
                                 DateUploaded = flickrPhoto.DateUploaded,
                                 PhotoId = flickrPhoto.PhotoId,
                                 UserName = person.UserName,
                                 RealName = person.RealName,
                                 UserCountry = person.Location,
                                 Title = flickrPhoto.Title,
                                 SmallUrl = flickrPhoto.SmallUrl,
                                 Views = flickrPhoto.Views.HasValue ? flickrPhoto.Views.Value : 0,
                                 BuddyIconUrl = person.BuddyIconUrl,
                                 Tags = new List<string>(),
                                 Description =
                                     !string.IsNullOrEmpty(flickrPhoto.Description) ? flickrPhoto.Description : randomDescription,
                             };

                {
                    var perc = (int)(((currentPhoto - 0.3) / allPhotos.Count) * 100);
                    if (backgroundWorker != null)
                        backgroundWorker.ReportProgress(perc);
                }

                var tagCollection = _flickr.TagsGetListPhoto(flickrPhoto.PhotoId);
                foreach (var tag in tagCollection)
                {
                    photo.Tags.Add(tag.TagText);
                }

                {
                    var perc = (int)(((currentPhoto - 0.1) / allPhotos.Count) * 100);
                    if (backgroundWorker != null)
                        backgroundWorker.ReportProgress(perc);
                }

                var vsp = VisualProperties.Default();
                vsp.X = Math.Max((random.NextDouble() * s.LandscapeWidth) - vsp.Width, 0);
                vsp.Y = Math.Max((random.NextDouble() * s.LandscapeHeight) - vsp.Height, 0);

                Dispatcher.Invoke(DispatcherPriority.Normal, (SendOrPostCallback)delegate
                                                                                     {
                                                                                         MyRootCollection.Add(photo, vsp);
                                                                                     }, null);

                var percentage = (int)(((double)currentPhoto / allPhotos.Count) * 100);

                if (backgroundWorker != null)
                    backgroundWorker.ReportProgress(percentage);

                currentPhoto++;
            }
        }

        private void SearchByWord()
        {
            if (!String.IsNullOrEmpty(SearchText))
            {
                //MyRootCollection.Clear();
                IsSearchActive = true;
                _searchByWordWorker.RunWorkerAsync(SearchText);
            }
        }

        private void DynamicQueryOnRootCollection(string word)
        {
            foreach (var item in MyRootCollection.Where(i => i.Item1 is FlickrPhoto))
            {
                var photo = (FlickrPhoto)item.Item1;
                var props = item.Item2;

                if (string.IsNullOrWhiteSpace(word) || photo.Tags.Any(tag => tag.ToLower().Contains(word.ToLower())))
                {
                    props.Opacity = 1;
                    props.Width = props.Height = 100;
                }
                else
                {
                    props.Opacity = 0.5;
                    props.Width = props.Height = 50;
                }
            }
        }

        private void InitSearchByWordWorker()
        {
            _searchByWordWorker = new BackgroundWorker
                                      {
                                          WorkerReportsProgress = true
                                      };
            _searchByWordWorker.DoWork += LoadPictures;
            _searchByWordWorker.ProgressChanged += OnSearchByWordWorkerProgressChanged;
            _searchByWordWorker.RunWorkerCompleted += OnSearchByWordWorkerCompleted;
        }

        #endregion

        #region event handling

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            try
            {
                ConnectToDatabase();
            }
            catch
            {
                Application.Current.Shutdown(0);
                return;
            }

            _flickr = new Flickr(FlickrApiKey);

            MyRootCollection = LoadCollection();
            MyRootCollection.CollectionChanged += OnRootCollectionChanged;

            var s = Settings.Default;
            InformationLandscape.Width = s.LandscapeWidth;
            InformationLandscape.Height = s.LandscapeHeight;

            ElementCount = MyRootCollection.Count;

            InitSearchByWordWorker();
        }

        private void OnRootCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ElementCount = MyRootCollection.Count;
        }

        private void OnSearchByWordWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            IsSearchActive = false;
        }

        private void OnSearchByWordWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            Dispatcher.BeginInvoke(DispatcherPriority.Render, (SendOrPostCallback)delegate { SearchProgress = e.ProgressPercentage; }, null);
        }

        private void OnSearchTextBoxKeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Return)
            {
                SearchByWord();
            }
        }

        private void OnSelectionFinished(object sender, SelectionEventArgs e)
        {
            IsSelectionEnabled = false;

            var portal = new ZPortal();
            var portalShape = new ZPortalShape();


            // Set Geometry from PortalShape to the Selectionshape
            var geometry = e.Selection.SelectionGeometry;

            // Apply Landscapetransformations on the Selectiongeometry to avoid wrong size and position of the PortalShape
            var pan = InformationLandscape.Animation.Position.Pan;
            var zoom = InformationLandscape.Animation.Position.Zoom;

            var group = new TransformGroup();

            var st = new ScaleTransform(1 / zoom, 1 / zoom);
            var tt = new TranslateTransform(-pan.X, -pan.Y);

            group.Children.Add(st);
            group.Children.Add(tt);

            geometry.Transform = group;
            portalShape.ShapeGeometry = geometry;

            //// Set HitTestType from PortalShape to FlickrPhotoView, that the shape recognizes only Photos from Flickr
            portalShape.HitTestTypes = new Type[] { typeof(FlickrPhotoView) };

            //// Connect the Portal with the Portalshape
            portal.PortalShape = portalShape;   //TODO: Crosreferenz -> keine Gleichheit bei BaseX

            //// Apply VisualProperties (including a Template) to our Portal and our Portalshape
            var vp = VisualProperties.Default();
            vp.TemplateId = "PortalShapeTemplate";
            vp.X = geometry.Bounds.X;
            vp.Y = geometry.Bounds.Y;
            vp.ZIndex = 100000000;

            var hsvp = VisualProperties.Default();
            hsvp.TemplateId = "PortalTemplate";
            hsvp.Width = 496;
            hsvp.Height = 351;
            hsvp.ZIndex = 100000000;

            bool linkAdded = false;
            if (MyRootCollection.BackendName.ToLower() == "db4o")
            {
                //db4o ruft kein INotifyPropertyChanged auf remote seite auf -> vorher hinzufügen
                //da aber link zwischen zwei IPersistables: BaseX muss erst davon wissen um zu verlinken
                LinkManager.AddNonDirectionalLink(portal, portalShape);
                linkAdded = true;
            }

            // Add both (PortalShape + Portal) into our Landscape
            MyRootCollection.Add(portalShape, vp);
            MyRootCollection.Add(portal, hsvp);

            if (!linkAdded)
            {
                LinkManager.AddNonDirectionalLink(portal, portalShape);
            }

            //// Apply a manual Hittest on the Portalshape
            portalShape.InvalidateShape();
        }

        private void OnClearLandscapeClick(object sender, RoutedEventArgs e)
        {
            MyRootCollection.Clear();
        }

        private void OnSortClick(object sender, RoutedEventArgs e)
        {
            var x = (int)Math.Sqrt(MyRootCollection.OfType<FlickrPhoto>().Count()) + 1;
            var currentPhoto = 0;

            var photos = LandscapeCanvas.Children.OfType<FlickrPhotoView>().ToList<FlickrPhotoView>();

            for (var i = 0; i <= x * 120; i += 120)
            {
                for (var j = 0; j <= x * 120; j += 120)
                {
                    if (currentPhoto < photos.Count)
                    {
                        var zpvm = (FlickrPhotoViewModel)photos[currentPhoto].DataContext;
                        var zp = zpvm.Model;
                        var props = MyRootCollection.GetVisualProperties(zp.DatabaseId);

                        var tg = new TransformGroup();

                        #region Animate X

                        var da = new DoubleAnimation(0, j + (InformationLandscape.ActualWidth / 2) - (x * (double)120 / 2) - props.X, new Duration(new TimeSpan(0, 0, 0, 1)));
                        var tt = new TranslateTransform();
                        tg.Children.Add(tt);
                        tt.BeginAnimation(TranslateTransform.XProperty, da);

                        #endregion

                        #region Animate Y

                        var da2 = new DoubleAnimation(0, i + (InformationLandscape.ActualHeight / 2) - (x * (double)120 / 2) - props.Y, new Duration(new TimeSpan(0, 0, 0, 1)));
                        var tt2 = new TranslateTransform();
                        tg.Children.Add(tt2);
                        tt2.BeginAnimation(TranslateTransform.YProperty, da2);
                        photos[currentPhoto].RenderTransform = tg;

                        #endregion

                        currentPhoto++;
                    }
                }
            }

        }

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string name)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(name));
        }

        #endregion

        private void OnConnectionFinished(object sender, ConnectionEventArgs e)
        {
            var connection = e.Connection;

            var viewModel1 = connection.Source.DataContext as BaseViewModel;
            var viewModel2 = e.Connection.Target.DataContext as BaseViewModel;

            if (viewModel1 == null || viewModel2 == null)
                return;

            LinkManager.AddDirectionalLink(viewModel1.Model as IInformationItem, viewModel2.Model as IInformationItem, Direction.SourceToTarget);
        }

        private void OnConnectionCancelled(object sender, ConnectionEventArgs e)
        {
            var shape = e.Connection.ConnectionGeometry;

            var elements = ZHitTestHelper.GetElementsInGeometry<ShapeToShapeConnectionView>(shape, InformationLandscape).ToList().Distinct();
            Console.WriteLine();

            foreach (var view in elements)
            {
                view.Background = Brushes.Yellow;

                var link = ((view.DataContext as ShapeToShapeConnectionViewModel).Model as ShapeToShapeConnection).Link;

                Console.WriteLine(link);
            }
        }

        private void OnViewportReceived(object sender, ViewportEventArgs e)
        {
            var to = InformationLandscape.GetCenteredViewPosition(e.Viewport.Bounds);
            InformationLandscape.Goto(to, true);
        }

        #region Database

        private void ConnectToDatabase()
        {
            Opacity = 0.5;

            var s = Settings.Default;

            var ds = DataBackendSelection.Instance;

            //                        var res = MessageBox.Show("Do you want to clear the database?", "Clear database?", MessageBoxButton.YesNo);
            //                        clearDatabase = res == MessageBoxResult.Yes;

            const bool clearDatabase = false;

            var dict = new Dictionary<string, object>();

            dict["host"] = s.DatabaseHost;
            dict["port"] = s.DatabasePort;
            dict["username"] = s.DatabaseUsername;
            dict["password"] = s.DatabasePassword;
            dict["databaseName"] = s.DatabaseName;

            if (ds.ActiveConnection.GetType().Name.StartsWith("BaseX"))
            {
                dict["eventName"] = "ZOILSamplePhotoBrowserEvent";
                dict["useUpdIndex"] = true;
                dict["useAcceptRemotelyCreatedObjectCondition"] = true;
                dict["useLoadXQuery"] = "for $id in //DatabaseId return zoil:getPairById($id)";
                dict["useCommitToDatabaseThrottleInterval"] = TimeSpan.FromMilliseconds(500);
                dict["clearIf"] = clearDatabase;
                dict["interactive"] = false;
            }
            else if (ds.ActiveConnection.GetType().Name.StartsWith("Db4o"))
            {
                dict["useCommitInterval"] = TimeSpan.FromMilliseconds(50);
            }

            try
            {
                ds.ActiveConnection.ConnectAndSetAsDefaultCollection(dict);
            }
            catch (Exception e)
            {
                if (ds.ActiveConnection.CanStartLocalDatabase())
                {
                    var result = MessageBox.Show("Could not connect to database. Would you like to start a local database?", "Connection Error", MessageBoxButton.YesNo, MessageBoxImage.Question);

                    if (result == MessageBoxResult.Yes)
                    {
                        ds.ActiveConnection.StartLocalDatabase(dict);
                        ConnectToDatabase();
                    }
                    else
                        Application.Current.Shutdown(0);
                }
                else
                    throw new ApplicationException("Fatal Error: " + e.Message);
            }

            Opacity = 1.0;
        }

        #endregion
    }
}
